iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0

[Day12] Clojure Flow Control (2) when / while / doseq

早安!

原本流程控制只想用一天的篇幅,但發現有好多內容可以寫的,

所以就想順勢擴充成3篇文章,

拆分文章的好處就是盡量讓clojure初學者好消化易吸收無負擔~

跟乾糧比起來肉湯罐罐好消化易吸收

今天就來解說第二part: when / while / doseq 吧!

第11天的文章在最後的例子稍稍帶到for

(for [x (range 1 6)]
     (* x x))
(1 4 9 16 25 36)

第12天的農場舉一反三篇的for 有一個很好的例子:

(let [map {:cat 5 :dog 3 :chicken 15 :cow 22 :sheep 46 }]
  (select-keys map (for [[key value] map :when (even? value)] key)))
  
=>{:cow 22, :sheep 46}

有沒有發現for可以跟不同條件組合起來?

常見的有when / while。
接下來我們就先來看看之前有印象的when吧!

when

Usage: (when test & body)
Evaluates test. If logical true, evaluates body in an implicit do.

如果if條件符合,do 執行接下來的指令,但不像if一樣需要有else的條件

The when operator is like a combination of if and do, but with no else branch.

(nil? nil)
=> true

;;囉唆的另一種寫法: when
(when (nil? nil) true)
=> true

---

(empty? [])
=> true

;;囉唆的另一種寫法: when
(when (empty? []) true)
=> true

?囉唆的另一種寫法只是為了讓我們多練習一下when的括弧撰寫結構,小試身手而已啦!

when是(if .. do ..)的macro(巨集),可以用 macroexpand來展開看原本的樣子:

(macroexpand '(when  (empty? []) true))
=>(if (empty? []) (do true))

之後的鐵人賽也會介紹其他的macro,敬請期待歐!

when在for裡面

舉一反三的結構長這樣:

(select-keys map 
  (for [[key value] 
       map 
       :when (even? value)] 
  key))

試著用最簡單的例子出發,vec裡1到6的element乘以3,符合偶數條件的再撈出來:

(for [x [1 2 3 4 5 6]
      :let [y (* x 3)]
      :when (even? y)]
  y)
;;=> (6 12 18)

當然,如果y乘以的是偶數那就會全撈出來啦!

(for [x [1 2 3 4 5 6]
      :let [y (* x 10)]
      :when (even? y)]
  y)
;;=> (10 20 30 40 50 60)

when-first

查了小抄之後,發現看來when的變形系列跟if一樣,也不少呢!

if
if-not
if-let
if-some

;;
when
when-not
when-let
when-some
when-first

來研究一下when變形怪之一:when-first!

(when-first bindings & body)
(when-first [a [1 2 3]] a)

bindings => x xs
Roughly the same as 
(when (seq xs) (let [x (first xs)] body)) 
but xs is evaluated only once

昨天有提到let可以做binding(綁定)的例子

(for [x (range 1 6) 
      :let [square-x (* x x) 
            cube-x (* x x x)]] 
  [x square-x cube-x])
  
=> ([1 1 1] [2 4 8] [3 9 27] [4 16 64] [5 25 125])

when-first的第一個接的就是預設binding,不需特意使用let囉

來舉幾個例子試試看:

(when-first [first-element [1 2 3 4]] first-element)
=> 1


(when-first [first-element ["1 2 3 4"]] first-element)
=>"1 2 3 4"

(when-first [first-element ["1"]] first-element)
=> "1"

(when-first [first-element []] first-element)
=> nil

while

(while test & body)

Repeatedly executes body while test expression is true. Presumes
some side-effect will cause test to become false/nil. Returns nil

side-effect也是要在後面章節細談的東東。我們先來看例子就好:

從1開始遞增印出小於6的正數

(def a (atom 1))

(while ( < @a 6 )
  (do (println @a)
  (swap! a inc)))

=>
1
2
3
4
5
nil

也可以舉個反過來寫的例子:
印出由6往下遞減1,一直到大於0的正數

(def a (atom 5))                                

(while (pos? @a)
  (println @a)
  (swap! a dec))
  
5
4
3
2
1
nil

while最重要的是要有break loop的條件,
如果把dec改成inc

(def a (atom 5))                                

(while (pos? @a)
  (println @a)
  (swap! a inc))
  
=>...
...
...

你會聽到電腦風扇開始起飛,然後就stackoverflow啦 ^_^

同樣都是好用的迭代語法,我們來比較一下 whenwhile

說到stackoverflow,來看一看stackoverflow這篇討論提到:

:when iterates over the bindings, but only evaluates the body of the loop when the condition is true.

;;回傳除了5之外,1到10的數字
(for [x (range 1 11) :when (not= x 5)] x)
=> (1 2 3 4 6 7 8 9 10)

:while iterates over the bindings and evaluates the body until the condition is false

;;回傳1到10之中小於5的數字
(for [x (range 1 11) :while (not= x 5)] x)
=> (1 2 3 4)

有沒有覺得以上的說明非常清晰易懂!

doseq

昨天講到for可以超展開,

(for [x (range 1 6) 
      :let [square-x (* x x) 
            cube-x (* x x x)]] 
  [x square-x cube-x])
  
=> ([1 1 1] [2 4 8] [3 9 27] [4 16 64] [5 25 125])

今天也想提一下Flow Control另一個sequence base的function

doseq做收尾

;;語法
(doseq seq-exprs & body)

Repeatedly executes body (presumably for side-effects) 
withbindings and filtering as provided by "for".  
Does not retain the head of the sequence. 
Returns nil.

來用doseq表達一下,Cartesian cross笛卡爾cross, 在集合論中(Set theory)代表所有可能的有序對組成的集合

(doseq [vec1 ["a" "b" "c"]
               vec2 ["x" "y" "z"]]
         (println vec1 vec2))
=>
a x
a y
a z
b x
b y
b z
c x
c y
c z
nil         

短短的三行就成功印出超展開的九宮格(?)
有沒有感受到clojure語法可以簡單實作迭代的威力呀?

明天來討論另外三個我覺得想推薦給大家,
在條件/流程控制的常用function :D

case cond doall


上一篇
[Day11] Clojure Flow Control (1) 基礎篇 if / if-not / for
下一篇
[Day13] Clojure Flow Control (3) case / cond / doall
系列文
後端Developer實戰ClojureScript: Reagent與前端框架 Reframe30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言